#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import functools
import glob
import json
import os
import re
import shutil
import stat
import time
import uuid

from common.common import execute_cmd, write_content_to_file
from common.common_models import ActionResult
from common.const import SysData
from sqlserver import log
from sqlserver.commons.const import ParamConstant, SQLServerStrConstant
from sqlserver.commons.sqlserver_parse_param import VDIAPINAME


def retry_when_return_false(retry_times=3, delay=1, logger=None):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            ret = None
            for _ in range(retry_times):
                try:
                    ret = func(*args, **kwargs)
                    if ret:
                        return ret
                except Exception as exception:
                    if not _:
                        raise exception
                    if logger is not None:
                        logger.warning(f'retrying in {delay} seconds...')
                time.sleep(delay)
            return ret

        return wrapper

    return decorator


def check_path_legal(file_path):
    real_dir_path = os.path.realpath(file_path)
    if real_dir_path.find("mnt") != -1:
        return
    system_drive_up = os.getenv("SystemDrive").upper()
    system_drive_down = system_drive_up.lower()
    if real_dir_path.startswith(system_drive_up) or \
            real_dir_path.startswith(system_drive_down):
        if os.path.dirname(real_dir_path) != os.path.realpath(ParamConstant.RESULT_PATH) \
                and os.path.dirname(real_dir_path) != os.path.realpath(ParamConstant.PARAM_FILE_PATH):
            log.error("File path not support to write")
            raise Exception("File path not support to write")


def exec_pw_shell(shell_cmd):
    filename = f'{uuid.uuid4()}.ps1'
    tmp_script = os.path.join(ParamConstant.PARAM_FILE_PATH + '/', filename)
    try:
        write_content_to_file(tmp_script, shell_cmd)
    except Exception as e_info:
        log.error(f"error:{e_info}")
        return False, ''
    cmd = f'{ParamConstant.POWER_SHELL_PATH} {tmp_script}'
    log.info(f"cmd:{cmd}")
    ret, std_out, std_err = execute_cmd(cmd)
    os.remove(tmp_script)
    if int(ret) != 0:
        log.error(f"exec_pw_shell cmd:{cmd} failed, {std_out}, {std_err}")
        return False, ''
    return True, std_out


def output_execution_result_ex(file_path, payload):
    # 先将文件清空再写
    check_path_legal(file_path)
    json_str = json.dumps(payload)
    flags = os.O_WRONLY | os.O_CREAT
    modes = stat.S_IWUSR | stat.S_IRUSR | stat.S_IXUSR
    with os.fdopen(os.open(file_path, flags, modes), 'w', encoding="utf-8") as out_file:
        out_file.truncate(0)
        out_file.write(json_str)


def output_action_result(pid, code, body_err, message, err_params: list = None):
    """
    将actionResult写入到结果文件,供框架读取
    :return:
    """
    json_result = ActionResult(code=code, bodyErr=body_err, message=message, bodyErrParams=err_params)
    file_path = os.path.join(ParamConstant.RESULT_PATH, f"result{pid}")
    log.info(f"Write file.path:{file_path} action code: {code}, body_err: {body_err}, message: {message}")
    output_execution_result_ex(file_path, json_result.dict(by_alias=True))


def output_tmp_info(file_path, payload):
    check_path_legal(file_path)
    flags = os.O_WRONLY | os.O_CREAT
    modes = stat.S_IWUSR | stat.S_IRUSR | stat.S_IXUSR
    with os.fdopen(os.open(file_path, flags, modes), 'w') as out_file:
        out_file.write(payload)


def check_clean_dir(dir_path):
    # 清理目录下所有文件
    if not os.path.exists(dir_path):
        return
    check_path_legal(dir_path)
    for path in os.listdir(dir_path):
        new_path = os.path.join(dir_path, path)
        if os.path.isfile(new_path):
            try:
                os.remove(new_path)
            except Exception as err:
                log.error(f"Delete file failed: {err}!")
        elif os.path.isdir(new_path):
            shutil.rmtree(new_path, ignore_errors=True)


def check_del_dir_or_file(path):
    # 删除目录或文件
    if not os.path.exists(path):
        return
    check_path_legal(path)
    if os.path.isdir(path):
        shutil.rmtree(path, ignore_errors=True)
    if os.path.isfile(path):
        try:
            os.remove(path)
        except Exception as err:
            log.error(f"Delete file failed: {err}!")


def is_need_adjust_channel(std_out):
    if "OpenDevice fails" in std_out:
        return True
    pattern = r'Msg \d+, Level \d+, State \d+'
    if re.match(pattern, std_out):
        return False
    for code in SQLServerStrConstant.VDI_CHANNEL_ERROR_CODE:
        if code in std_out:
            return True
    return False


def get_env_variable(str_env_variable: str):
    env_variable = ''
    input_str = json.loads(SysData.SYS_STDIN)
    if input_str.get(str_env_variable):
        env_variable = input_str.get(str_env_variable)
    return env_variable


def get_key_value(key_name):
    """
    根据传入的key值，返回value值
    :param key_name: key值
    :return: value值
    """
    if not SysData.SYS_STDIN:
        return False, ""
    try:
        json_str = json.loads(SysData.SYS_STDIN)
    except Exception as e_info:
        return False, ""
    try:
        username = json_str.get(key_name)
    except Exception as e_info:
        return False, ""
    if not username:
        return False, ""
    return True, username


def read_first_line(in_out):
    line_list = in_out.split('\n')
    if len(line_list) > 0:
        return line_list[0].encode('utf-8').decode('utf-8')
    else:
        return ""


def check_is_sub_job(json_param):
    return bool(json_param.get("subJob"))


def check_is_sub_job_database(json_param):
    return bool(json_param.get("subJob")) and json_param.get("subJob").get("jobName", "") not in ['backup', 'restore']


def write_to_ps_script(cmd, file_name):
    tmp_script = os.path.join(ParamConstant.PARAM_FILE_PATH, file_name)
    flags = os.O_WRONLY | os.O_CREAT
    modes = stat.S_IWUSR | stat.S_IRUSR
    with os.fdopen(os.open(tmp_script, flags, modes), 'w+') as out_file:
        out_file.write(cmd)


def exec_pw_shell_filename(shell_cmd):
    cmds = f'{VDIAPINAME.ps_loc} {shell_cmd}'
    ret, std_out, std_err = execute_cmd(cmds)
    log.info(f"exec powershell cmd: {cmds}, ret: {ret}, std_out: {std_out}, std_err: {std_err}")
    if int(ret) != 0:
        log.error(f"Exec pwshell cmd: {shell_cmd}  failed. Out: {std_out}, err:{std_err}.")
        return False, '', std_err
    return True, std_out, std_err


def get_channel_number(_json_param):
    channel_number = SQLServerStrConstant.DEFAULT_CHANNEL
    extend_info = _json_param.get("job", {}).get("extendInfo", {})
    if not extend_info:
        return channel_number
    backup_task_sla = json.loads(extend_info.get("backupTask_sla", {}))
    if not backup_task_sla:
        return channel_number
    policy_list = backup_task_sla.get("policy_list", [])
    if not policy_list:
        return channel_number
    ext_parameters = policy_list[0].get("ext_parameters", {})
    if not ext_parameters:
        return channel_number
    channel_number = int(ext_parameters.get("channel_number", SQLServerStrConstant.DEFAULT_CHANNEL))
    return channel_number


def minimum_vdi_virtual_device(channel_number: int):
    if channel_number <= 2:
        return 2
    return channel_number


def get_restore_channel(path, database):
    files = os.listdir(path)
    database_bak_files = [f for f in files if f.startswith(f"{database}.bak")]
    num = sum([1 for _ in database_bak_files])
    if num != 0:
        return num
        # 针对重命名的情况特殊处理
    bak_file = os.path.join(path, "*.bak*")
    return len(glob.glob(bak_file))


def is_new_restore_path(job_param):
    value_1 = job_param.get("job", {}).get("extendInfo", {}).get("isRestoreNew", "")
    value_2 = job_param.get("job", {}).get("extendInfo", {}).get("extendInfo", {}).get("isRestoreNew", "")
    if value_1.lower() == "true" or value_2.lower() == "true":
        bool_value = True
    else:
        bool_value = False
    return bool_value
